package com.object.oriented.exam.system3.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.object.oriented.exam.system1.dao.ChoiceMapping;
import com.object.oriented.exam.system1.dao.KnowledgeMapping;
import com.object.oriented.exam.system1.dao.PictureMapping;
import com.object.oriented.exam.system1.dao.ProblemMapping;
import com.object.oriented.exam.system1.entity.Choice;
import com.object.oriented.exam.system1.entity.Picture;
import com.object.oriented.exam.system1.entity.Problem;
import com.object.oriented.exam.system3.entity.dto.PaperVo;
import com.object.oriented.exam.system3.mapper.PaperMapper;
import com.object.oriented.exam.system3.mapper.PaperProblemMapper;
import com.object.oriented.exam.system3.entity.dto.Population;
import com.object.oriented.exam.system3.entity.dto.SurveyRuleParam;
import com.object.oriented.exam.system3.entity.po.PaperProblem;
import com.object.oriented.exam.system3.service.AutoGenerateService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import com.object.oriented.exam.system3.entity.po.Paper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class AutoGenerateServiceImpl extends ServiceImpl<PaperMapper,Paper> implements AutoGenerateService {

    //最少迭代次数
    private final int runCount = 100;

    //最大迭代次数
    private final int maxCount = 1000;

    //最小预期值
    private final double expand = 0.9;

    //难度系数占比
    private static final double difficultyRatio = 0.7;

    //知识点覆盖率占比
    private static final double knowledgeRatio = 1 - difficultyRatio;

    /**
     * 变异概率
     */
    private static final double mutationRate = 0.085;
    /**
     * 精英主义
     */
    private static final boolean elitism = true;
    /**
     * 淘汰数组大小
     */
    private static final int tournamentSize = 5;

    @Resource
    private ProblemMapping problemMapping;

    @Resource
    private KnowledgeMapping knowledgeMapping;

    @Resource
    private PaperMapper paperMapper;

    @Resource
    private PaperProblemMapper paperProblemMapper;

    @Resource
    private ChoiceMapping choiceMapping;

    @Resource
    private PictureMapping pictureMapping;

    @Override
    public Long autoGeneratePaper(SurveyRuleParam surveyRuleParam) {
        //获取知识点个数
        int kNum = knowledgeMapping.selectCount(new QueryWrapper<>());
        //获取所有题目
        List<Problem> problemList = problemMapping.selectList(new QueryWrapper<>());
        //根据题型划分
        Map<Integer, List<Problem>> typeMap = problemList.stream().collect(Collectors.groupingBy(Problem::getType));
        //获取初始生态
        Population population = new Population(100, typeMap, surveyRuleParam, kNum, difficultyRatio, knowledgeRatio);
        //计算迭代次数
        int count = 0;
        while (count < this.runCount || (population.getFitness().getAdaptationDegree() < this.expand && count < maxCount)) {
            count++;
            population = evolvePopulation(population, surveyRuleParam, typeMap, kNum);
        }
        //分数计算
        Paper paper = population.getFitness();
        paper.setTotalScore(surveyRuleParam.getScore());
        paper.setIsAuto(true);
        paperMapper.insert(paper);
        calculateSumScore(paper, surveyRuleParam.getScore());
        for (PaperProblem paperProblem : paper.getPaperProblems()) {
            paperProblemMapper.insert(paperProblem);
        }
        return paper.getId();
    }

    @Override
    public List<PaperVo> getList() {
        List<PaperVo> result = new ArrayList<>();
        QueryWrapper<Paper> pWrapper = new QueryWrapper<>();
        pWrapper.orderByDesc("id");
        List<Paper> papers = paperMapper.selectList(pWrapper);
        Map<Long,Problem> pMap = problemMapping.selectList(new QueryWrapper<>()).stream().collect(Collectors.toMap(Problem::getId,p -> p));
        for (Paper paper : papers) {
            QueryWrapper<PaperProblem> ppWrapper = new QueryWrapper<>();
            ppWrapper.eq("paper_id",paper.getId());PaperVo paperVo = new PaperVo();
            paperVo.setPaper(paper);
            paperVo.setProblems(new ArrayList<>());
            List<PaperProblem> ppList = paperProblemMapper.selectList(ppWrapper);
            for (PaperProblem paperProblem : ppList) {
                Problem problem = pMap.get(paperProblem.getProblemId());
                problem.setDefaultScore(paperProblem.getScore());
                Map<String,Object> choiceMap = new HashMap<>();
                choiceMap.put("problem_id",problem.getId());
                List<Object> resultProblem = new ArrayList<>();
                if(problem.getType() == 1){
                    List<Choice> choices = choiceMapping.selectByMap(choiceMap);
                    if(choices != null){
                        resultProblem.addAll(choices);
                    }
                }else{
                    List<Picture> pictures = pictureMapping.selectByMap(choiceMap);
                    if(pictures != null){
                        resultProblem.addAll(pictures);
                    }
                }
                problem.setExpend(resultProblem);
                paperVo.getProblems().add(problem);
            }

            result.add(paperVo);

        }
        return result;
    }

    private Population evolvePopulation(Population population, SurveyRuleParam rule, Map<Integer, List<Problem>> typeMap, int kNum) {
        Population newPopulation = new Population();
        newPopulation.setPaperList(new ArrayList<>());
        int elitismOffset;
        // 精英主义
        if (elitism) {
            elitismOffset = 1;
            // 保留上一代最优秀个体
            newPopulation.getPaperList().add(population.getFitness());
        }
        // 种群交叉操作，从当前的种群pop来创建下一代种群newPopulation
        for (int i = elitismOffset; i < population.getPaperList().size(); i++) {

            // 较优选择parent
            Paper parent1 = select(population);
            Paper parent2 = select(population);

            while (parent2 == parent1) {
                parent2 = select(population);
            }

            // 交叉 将parent1的片段插入替换parent2中
            Paper child = crossover(parent1, parent2, typeMap);
            if(child == null){
                //交叉失败
                continue;
            }
            //为paper设置临时id用来区分身份
            child.setId((long) i);
            newPopulation.getPaperList().add(child);
        }

        /*for (Paper paper : population.getPaperList()) {
            int[] a = new int[100];
            for (Problem problem : paper.getProblems()) {
                if(a[Math.toIntExact(problem.getId())] > 0){
                    System.out.println(paper);
                }
                a[Math.toIntExact(problem.getId())] += 1;

            }
        }*/
        // 种群变异操作
        Paper tmpPaper;
        for (int i = elitismOffset; i < newPopulation.getPaperList().size(); i++) {
            tmpPaper = newPopulation.getPaperList().get(i);
            //变异 每个题目都看能不能换一波
            mutate(tmpPaper, typeMap);
            // 计算知识点覆盖率与适应度
            tmpPaper.calculateAdaptationDegree(rule, kNum, difficultyRatio, knowledgeRatio);
        }
        for (Paper paper : population.getPaperList()) {
            int[] a = new int[100];
            for (Problem problem : paper.getProblems()) {
                if(a[Math.toIntExact(problem.getId())] > 0){
                    System.out.println(paper);
                }
                a[Math.toIntExact(problem.getId())] += 1;

            }
        }
        return newPopulation;
    }

    private Paper select(Population population) {
        Population pop = new Population();
        pop.setPaperList(new ArrayList<>());
        for (int i = 0; i < tournamentSize; i++) {
            //随机添加5(tournamentSize)个
            pop.getPaperList().add(population.getPaperList().get((int) (Math.random() * population.getPaperList().size())));
        }
        //从5个中选个最好的
        return pop.getFitness();
    }

    public Paper crossover(Paper parent1, Paper parent2, Map<Integer, List<Problem>> typeMap) {
        //创建一个问卷
        Paper child = new Paper();
        child.setProblems(new ArrayList<>(parent1.getProblems()));
        //获取随机长度
        Random random = new Random();
        int s1 = random.nextInt(parent1.getProblems().size()) % (parent1.getProblems().size() - 1);
        int s2 = random.nextInt(parent2.getProblems().size()) % (parent2.getProblems().size() - 1);
        // parent1的startPos endPos之间的序列，会被遗传到下一代
        int startPos = Math.min(s1, s2);
        int endPos = Math.max(s1, s2);
        //截取parent1随机长度的question
        for (int i = startPos; i < endPos; i++) {
            //将交叉的问题片段保存进问卷
            child.getProblems().set(i, parent1.getProblems().get(i));
        }

        // 继承parent2中未被child继承的question
        // 防止出现重复的元素

        //截取parent2的
        for (int i = 0; i < startPos; i++) {
            if (!child.containsQuestion(parent2.getProblems().get(i))) {
                child.getProblems().set(i, parent2.getProblems().get(i));
            } else {
                // getProblems().getArray()，补充缺失的题目用来选择指定的题目类型和知识点的试题数组
                List<Problem> q = typeMap.get(parent2.getProblems().get(i).getType());
                //随机拿一个试题中的东西组合（要在知识点范围内的且是该题型）
                int count = 0;
                while(!child.containsQuestion(parent2.getProblems().get(i)) && count++ < 100){
                    child.getProblems().set(i,q.get((int) (Math.random() * q.size())));
                }
                if(count >= 100){
                    return null;
                }
            }
        }
        for (int i = endPos; i < parent2.getProblems().size(); i++) {
            if (!child.containsQuestion(parent2.getProblems().get(i))) {
                child.getProblems().set(i, parent2.getProblems().get(i));
            } else {
                // getProblems().getArray()，补充缺失的题目用来选择指定的题目类型和知识点的试题数组
                List<Problem> q = typeMap.get(parent2.getProblems().get(i).getType());
                //随机拿一个试题中的东西组合（要在知识点范围内的且是该题型）

                int count = 0;
                while(!child.containsQuestion(parent2.getProblems().get(i)) && count++ < 100){
                    child.getProblems().set(i,q.get((int) (Math.random() * q.size())));
                }
                if(count >= 100){
                    return null;
                }

            }
        }

        return child;
    }

    public static void mutate(Paper Paper, Map<Integer, List<Problem>> typeMap) {
        Problem tmpProblem;
        int index;
        for (int i = 0; i < Paper.getProblems().size(); i++) {
            if (Math.random() < mutationRate) {
                // 进行突变，第i道
                tmpProblem = Paper.getProblems().get(i);
                // 从题库中获取和变异的题目类型一样分数相同的题目（不包含变异题目）
                List<Problem> q = typeMap.get(tmpProblem.getType());
                // 随机获取一道
                int count = 100;
                //尝试获取一道试卷中没有的题目，若获取不到则放弃变异操作
                while (count-- > 0) {
                    index = (int) (Math.random() * q.size());
                    if (!Paper.containProblem(q.get(index))) {
                        Paper.getProblems().set(i, q.get(index));
                    }
                }
            }
        }
    }

    private void calculateSumScore(Paper paper, int goalScore) {
        Map<Integer, List<Problem>> problemMap = paper.getProblems().stream().collect(Collectors.groupingBy(Problem::getType));
        //每种题型的得分
        Map<Integer, Double> qScore = new HashMap<>();
        double sum = 0;
        for (Integer i : problemMap.keySet()) {
            double temp = 0.0;
            for (Problem j : problemMap.get(i)) {
                //累加每种题型的分数
                temp += j.getDefaultScore();
            }
            //记录每种题型的分数
            qScore.put(i, temp);
            sum += temp;
        }
        Map<Integer, Double> singeScore = new HashMap<>();
        for (Integer i : qScore.keySet()) {
            //计算每个题目的大概分数
            singeScore.put(i, (qScore.get(i) / sum * goalScore / problemMap.get(i).size()));
        }
        // int lastScore = goalScore - singeSum;
        List<Problem> qList = paper.getProblems();
        List<PaperProblem> ppList = new ArrayList<>();
        for (int i = 0; i < qList.size(); i++) {
            //重新设置每题分数 并处理无法分配的分数  + 100 是应为分数基本单位是 分
            ppList.add(new PaperProblem(qList.get(i).getId(),
                    paper.getId(),
                    (int)(singeScore.get(qList.get(i).getType())* 100 + 100)));
        }
        // for (int i = qList.size() - 1; i >= qList.size() - lastScore; i--) {
        //     //重新设置每题分数 并处理无法分配的分数  + 100 是应为分数基本单位是 分
        //     ppList.set(i,new PaperProblem(qList.get(i).getId(),paper.getId(),singeScore.get(qList.get(i).getType()) + 100));
        // }
        paper.setPaperProblems(ppList);
    }


}
